#include <linux/tty.h>
#include <asm/io.h>
#include <asm/system.h>

#define ORIG_X              (*(unsigned char *)0x90000)     // 光标列号 
#define ORIG_Y              (*(unsigned char *)0x90001)     // 光标行号
#define ORIG_VIDEO_PAGE     (*(unsigned short *)0x90004)    // 当前页号
#define ORIG_VIDEO_MODE     (*(unsigned char *)0x90006)     // 显示模式
#define ORIG_VIDEO_COLS     (*(unsigned char *)0x90007)     // 显示列数
#define ORIG_VIDEO_LINES    (25)                            // 显示行数
#define ORIG_VIDEO_EGA_AX   (*(unsigned short *)0x90008)    // [??]
#define ORIG_VIDEO_EGA_BX   (*(unsigned short *)0x9000a)    // 显示内存和色彩模式
#define ORIG_VIDEO_EGA_CX   (*(unsigned short *)0x9000c)    // 显示卡特性参数

// 定义显示器单色/彩色显示模式类型符号常数
#define VIDEO_TYPE_MDA      0x10    // 单色文本显示
#define VIDEO_TYPE_CGA      0x11    // CGA显示
#define VIDEO_TYPE_EGAM     0x20    // EGA/VGA单色模式
#define VIDEO_TYPE_EGAC     0x21    // EGA/VGA彩色模式

static unsigned char    video_type;         // 显示模式
static unsigned long    video_num_columns;  // 显示列数
static unsigned long    video_size_row;     // 每行字节数
static unsigned long    video_num_lines;    // 显示行数
static unsigned char    video_page;         // 初始页面
static unsigned long    video_mem_start;    // 显存起始地址
static unsigned long    video_mem_end;      // 显存结束地址
static unsigned short   video_port_reg;     // 显示控制器索引寄存器端口
static unsigned short   video_port_val;     // 显示控制器数据寄存器端口
static unsigned short   video_erase_char;   // 擦除字符属性与字符(0x0720)

// 以下这些变量用于屏幕卷屏操作
static unsigned long    origin;     // 用于EGA/VGA快速滚屏
static unsigned long    scr_end;    // 用于EGA/VGA快速滚屏
static unsigned long    pos;        // 当前光标对应的显存地址
static unsigned long    x, y;       // 当前光标位置
static unsigned long    top, bottom;// 滚动时顶行行号，底行行号

static unsigned char    attr = 0x07;// 字符属性（黑底白字）

static inline void gotoxy(unsigned int new_x, unsigned int new_y)
{
    if (new_x > video_num_columns || new_y >= video_num_lines)
        return;
    x = new_x;
    y = new_y;
    pos = origin + y * video_size_row + (x << 1);
}

static inline void set_origin(void)
{
    cli();
    outb_p(12, video_port_reg);
    outb_p(0xff&((origin-video_mem_start)>>9), video_port_val);
    outb_p(13, video_port_reg);
    outb_p(0xff&((origin-video_mem_start)>>1), video_port_val);
    sti();
}

static void scrup(void)
{
    if (video_type == VIDEO_TYPE_EGAC || video_type == VIDEO_TYPE_EGAM)
    {
        if (!top && bottom == video_num_lines) {
            origin += video_size_row;
            pos += video_size_row;
            scr_end += video_size_row;
            if (scr_end <= video_mem_end) {
                __asm__("cld\n\t"
                    "rep\n\t"
                    "stosw"
                    ::"a"(video_erase_char),
                    "c"(video_num_columns),
                    "D"(scr_end - video_size_row)
                    );
            }
            else {
                __asm__("cld\n\t"
                    "rep\n\t"
                    "movsd\n\t"
                    "movl %2, %%ecx\n\t"
                    "rep\n\t"
                    "stosw"
                    ::"a"(video_erase_char),
                    "c"((video_num_lines - 1) * video_num_columns >> 1),
                    "m"(video_num_columns),
                    "D"(video_mem_start),
                    "S"(origin)
                    );
                scr_end -= origin - video_mem_start;
                pos -= origin - video_mem_start;
                origin = video_mem_start;
            }
            set_origin();
        }
        else {
            __asm__("cld\n\t"
                "rep\n\t"
                "movsd\n\t"
                "movl %2, %%ecx\n\t"
                "rep\n\t"
                "stosw"
                ::"a"(video_erase_char),
                "c"((bottom - top - 1) * video_num_columns >> 1),
                "m"(video_num_columns),
                "D"(origin + video_size_row * top),
                "S"(origin + video_size_row * (top + 1))
                );
        }
    }
    else        /* Not EGA/VGA */
    {
        __asm__("cld\n\t"
            "rep\n\t"
            "movsd\n\t"
            "movl %2, %%ecx\n\t"
            "rep\n\t"
            "stosw"
            ::"a"(video_erase_char),
            "c"((bottom - top - 1) * video_num_columns >> 1),
            "m"(video_num_columns),
            "D"(video_mem_start + video_size_row * top),
            "S"(video_mem_start + video_size_row * (top + 1))
            );
    }
}

static void lf(void)
{
    if (y + 1 < bottom) {
        y++;
        pos += video_size_row;
        return;
    }
    scrup();
}

static void cr(void)
{
    pos -= x << 1;
    x = 0;
}

static void del(void)
{
    if (x) {
        pos -= 2;
        x--;
        *(unsigned short *)pos = video_erase_char;
    }
}

static inline void set_cursor(void)
{
    cli();
    outb_p(14, video_port_reg);
    outb_p(0xff&((pos-video_mem_start)>>9), video_port_val);
    outb_p(15, video_port_reg);
    outb_p(0xff&((pos-video_mem_start)>>1), video_port_val);
    sti();
}

void con_write(char *buf, int nr)
{
    int i;
    char c;

    for (i = 0; i < nr; i++) {
        c = buf[i];
        if (c > 31 && c < 127) {
            if (x >= video_num_columns) {    // 打印完一行后换行
                x -= video_num_columns;
                pos -= video_size_row;
                lf();
            }
            __asm__(
                "movb %1, %%ah\n\t"
                "movw %%ax, %2\n\t"
                ::"a"(c), "m"(attr), "m" (*(short *)pos));
            pos += 2;
            x++;
        }
        else if (c == 10 || c == 11 || c == 12) // '\n'，换行，使光标下移一格
            lf();
        else if (c == 13)       // '\r'，回车，使光标移至行首
            cr();
        else if (c == '\177')   // 八进制数，等于十进制127，删除
            del();
        else if (c == 8) {      // '\b'，退格
            if (x) {
                x--;
                pos -= 2;
            }
        }
        else if (c == 9) {      // '\t'，水平制表符
            c = 8 - (x & 7);
            x += c;
            pos += c << 1;
            if (x > video_num_columns) {
                x -= video_num_columns;
                pos -= video_size_row;
                lf();
            }
            c = 9;
        }
    }
    set_cursor();    // 打印完字符串后，重新设置光标
}

void con_init(void)
{
    char *display_desc = "????";
    char *display_ptr;

    video_num_columns = ORIG_VIDEO_COLS;
    video_size_row = video_num_columns * 2;
    video_num_lines = ORIG_VIDEO_LINES;
    video_page = ORIG_VIDEO_PAGE;
    video_erase_char = 0x0720;              // 擦除字符(0x20显示字符，0x07是属性)
    
    if (ORIG_VIDEO_MODE == 7)               // 如果为单色显示
    {
        video_mem_start = 0xb0000;
        video_port_reg = 0x3b4;
        video_port_val = 0x3b5;
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
        {
            video_type = VIDEO_TYPE_EGAM;
            video_mem_end = 0xb8000;
            display_desc = "EGAm";
        }
        else
        {
            video_type = VIDEO_TYPE_MDA;
            video_mem_end = 0xb2000;
            display_desc = "*MDA";
        }
    }
    else                                    // 如果不是，就是彩色显示
    {
        video_mem_start = 0xb8000;
        video_port_reg = 0x3d4;
        video_port_val = 0x3d5;
        if ((ORIG_VIDEO_EGA_BX & 0xff) != 0x10)
        {
            video_type = VIDEO_TYPE_EGAC;
            video_mem_end = 0xbc000;
            display_desc = "EGAc";
        }
        else
        {
            video_type = VIDEO_TYPE_CGA;
            video_mem_end = 0xba000;
            display_desc = "*CGA";
        }
    }

    // 打印正在使用的显示驱动程序
    display_ptr = ((char *)video_mem_start) + video_size_row - 8;
    while (*display_desc)
    {
        *display_ptr++ = *display_desc++;
        display_ptr++;
    }
    
    // 初始化用于滚屏的变量
    origin = video_mem_start;
    scr_end = video_mem_start + video_num_lines * video_size_row;
    top = 0;
    bottom = video_num_lines;

    gotoxy(ORIG_X, ORIG_Y);
}
